📋 Informazioni sull'Esercitazione
🎯 Obiettivi dell'Esercitazione
Questa esercitazione ti guiderà attraverso 5 esercizi progressivi che coprono tutti i concetti fondamentali della Programmazione Orientata agli Oggetti in Python che hai studiato nella lezione.
Gli esercizi sono ordinati per difficoltà crescente:
- Esercizio 1-2: Concetti base (classi, __init__, metodi)
- Esercizio 3: Property e incapsulamento
- Esercizio 4: Ereditarietà e polimorfismo
- Esercizio 5: Integrazione di più concetti
💡 Come Affrontare gli Esercizi
- Leggi attentamente la traccia e i requisiti
- Pianifica prima di scrivere codice
- Testa il tuo codice con gli esempi forniti
- Usa gli hint se rimani bloccato
- Confronta la tua soluzione con quella proposta solo dopo aver provato
- Non copiare le soluzioni: impari solo facendo!
🔐 Nota: Le soluzioni sono protette da password. Quando clicchi su "Mostra/Nascondi Soluzione", ti verrà chiesta la password fornita dal docente.
Classe Studente
⭐ Facile📝 Traccia
Crea una classe Studente che rappresenti uno studente universitario.
La classe deve permettere di memorizzare informazioni sullo studente e gestire i suoi voti.
✅ Requisiti
-
La classe deve avere un metodo
__init__che accetta:nome(stringa)cognome(stringa)matricola(stringa)
-
Deve avere un attributo
votiche è una lista vuota (inizializzata automaticamente) -
Implementa un metodo
aggiungi_voto(voto)che aggiunge un voto alla lista (il voto deve essere tra 18 e 30) -
Implementa un metodo
media_voti()che restituisce la media dei voti (0 se non ci sono voti) -
Implementa il metodo
__str__che restituisce una stringa del tipo: "Studente: Mario Rossi (Matricola: 12345) - Media: 27.5"
studente = Studente("Mario", "Rossi", "12345")
studente.aggiungi_voto(28)
studente.aggiungi_voto(30)
studente.aggiungi_voto(25)
print(studente) # Studente: Mario Rossi (Matricola: 12345) - Media: 27.67
print(f"Media: {studente.media_voti()}") # Media: 27.666666666666668
💡 Suggerimenti
- Ricorda di usare
selfcome primo parametro in tutti i metodi - Inizializza la lista
votinel metodo__init__ - Per calcolare la media usa
sum(self.voti) / len(self.voti) - Controlla che ci siano voti prima di calcolare la media (evita divisione per zero!)
- Per validare il voto usa
if 18 <= voto <= 30:
✅ Soluzione Proposta
class Studente: """Rappresenta uno studente universitario con i suoi voti.""" def __init__(self, nome, cognome, matricola): """ Inizializza uno studente. Args: nome (str): Nome dello studente cognome (str): Cognome dello studente matricola (str): Matricola univoca """ self.nome = nome self.cognome = cognome self.matricola = matricola self.voti = [] # Lista vuota per i voti def aggiungi_voto(self, voto): """ Aggiunge un voto alla lista se valido (18-30). Args: voto (int): Il voto da aggiungere Returns: bool: True se il voto è stato aggiunto, False altrimenti """ if 18 <= voto <= 30: self.voti.append(voto) print(f"✅ Voto {voto} aggiunto con successo!") return True else: print(f"❌ Voto {voto} non valido! Deve essere tra 18 e 30.") return False def media_voti(self): """ Calcola la media dei voti. Returns: float: La media dei voti, 0 se non ci sono voti """ if len(self.voti) == 0: return 0 return sum(self.voti) / len(self.voti) def __str__(self): """Rappresentazione testuale dello studente.""" media = self.media_voti() return f"Studente: {self.nome} {self.cognome} (Matricola: {self.matricola}) - Media: {media:.2f}" # Test della classe if __name__ == "__main__": studente = Studente("Mario", "Rossi", "12345") studente.aggiungi_voto(28) studente.aggiungi_voto(30) studente.aggiungi_voto(25) studente.aggiungi_voto(15) # Questo non verrà aggiunto (non valido) print(studente) print(f"Media: {studente.media_voti():.2f}")
Classe ContoBancario
⭐⭐ Facile-Medio📝 Traccia
Crea una classe ContoBancario che simuli un conto corrente bancario con
operazioni di deposito, prelievo e visualizzazione del saldo.
✅ Requisiti
-
Il metodo
__init__deve accettare:titolare(stringa)saldo_iniziale(float, default = 0.0)
-
Implementa un metodo
deposita(importo)che:- Aumenta il saldo dell'importo specificato
- Restituisce True se l'operazione ha successo
- Restituisce False se l'importo è negativo o zero
-
Implementa un metodo
preleva(importo)che:- Diminuisce il saldo dell'importo specificato
- Restituisce True se l'operazione ha successo
- Restituisce False se l'importo è negativo/zero o se non ci sono fondi sufficienti
-
Implementa un metodo
get_saldo()che restituisce il saldo corrente -
Implementa il metodo
__str__che mostra: "Conto di [titolare] - Saldo: €[saldo]"
conto = ContoBancario("Laura Bianchi", 1000.0)
print(conto) # Conto di Laura Bianchi - Saldo: €1000.00
conto.deposita(500) # True
conto.preleva(200) # True
conto.preleva(2000) # False (fondi insufficienti)
print(conto) # Conto di Laura Bianchi - Saldo: €1300.00
💡 Suggerimenti
- Usa un attributo privato
self._saldoper memorizzare il saldo - Prima di ogni operazione, controlla la validità dell'importo
- Nel prelievo, controlla che ci siano fondi sufficienti:
if self._saldo >= importo: - Usa la formattazione
{saldo:.2f}per mostrare 2 decimali
✅ Soluzione Proposta
class ContoBancario: """Rappresenta un conto corrente bancario.""" def __init__(self, titolare, saldo_iniziale=0.0): """ Inizializza un conto bancario. Args: titolare (str): Nome del titolare del conto saldo_iniziale (float): Saldo iniziale del conto (default: 0.0) """ self.titolare = titolare self._saldo = saldo_iniziale # Attributo protetto def deposita(self, importo): """ Deposita denaro sul conto. Args: importo (float): L'importo da depositare Returns: bool: True se operazione riuscita, False altrimenti """ if importo <= 0: print(f"❌ Importo non valido: €{importo:.2f}") return False self._saldo += importo print(f"✅ Depositati €{importo:.2f} - Nuovo saldo: €{self._saldo:.2f}") return True def preleva(self, importo): """ Preleva denaro dal conto. Args: importo (float): L'importo da prelevare Returns: bool: True se operazione riuscita, False altrimenti """ if importo <= 0: print(f"❌ Importo non valido: €{importo:.2f}") return False if self._saldo < importo: print(f"❌ Fondi insufficienti! Saldo: €{self._saldo:.2f}, Richiesto: €{importo:.2f}") return False self._saldo -= importo print(f"✅ Prelevati €{importo:.2f} - Nuovo saldo: €{self._saldo:.2f}") return True def get_saldo(self): """ Restituisce il saldo corrente. Returns: float: Il saldo del conto """ return self._saldo def __str__(self): """Rappresentazione testuale del conto.""" return f"Conto di {self.titolare} - Saldo: €{self._saldo:.2f}" # Test della classe if __name__ == "__main__": conto = ContoBancario("Laura Bianchi", 1000.0) print(conto) print() conto.deposita(500) conto.preleva(200) conto.preleva(2000) # Fallirà conto.deposita(-100) # Fallirà print() print(conto) print(f"Saldo finale: €{conto.get_saldo():.2f}")
Classe Prodotto con Property
⭐⭐⭐ Medio📝 Traccia
Crea una classe Prodotto che rappresenti un articolo in un negozio.
Usa le property per gestire il prezzo e lo sconto con validazione automatica.
✅ Requisiti
-
Il metodo
__init__deve accettare:nome(stringa)prezzo(float)quantita(int, default = 0)
-
Crea una property
prezzoche:- Restituisce il prezzo (getter)
- Imposta il prezzo solo se è positivo, altrimenti solleva
ValueError(setter)
-
Crea un attributo
sconto_percentuale(inizialmente 0) con una property che:- Restituisce lo sconto percentuale (getter)
- Imposta lo sconto solo se è tra 0 e 100 (setter)
-
Crea una property di sola lettura
prezzo_scontatoche:- Calcola e restituisce il prezzo finale con lo sconto applicato
- Formula:
prezzo * (1 - sconto_percentuale / 100)
-
Crea una property di sola lettura
valore_inventarioche:- Restituisce il valore totale dell'inventario:
prezzo_scontato * quantita
- Restituisce il valore totale dell'inventario:
-
Implementa
__str__per mostrare le informazioni del prodotto
prodotto = Prodotto("Laptop", 1000.0, 5)
print(prodotto) # Laptop - €1000.00 x 5 = €5000.00
prodotto.sconto_percentuale = 20
print(f"Prezzo scontato: €{prodotto.prezzo_scontato:.2f}") # €800.00
print(f"Valore inventario: €{prodotto.valore_inventario:.2f}") # €4000.00
prodotto.prezzo = 1200 # OK
prodotto.prezzo = -50 # Solleva ValueError
💡 Suggerimenti
- Usa
@propertyper il getter e@nome_property.setterper il setter - Gli attributi interni devono iniziare con underscore:
self._prezzo - Per property di sola lettura, non definire il setter
- Usa
raise ValueError("messaggio")per sollevare eccezioni - Ricorda che nelle property il calcolo deve usare
self.prezzo, nonself._prezzo
✅ Soluzione Proposta
class Prodotto: """Rappresenta un prodotto in un negozio con gestione sconto.""" def __init__(self, nome, prezzo, quantita=0): """ Inizializza un prodotto. Args: nome (str): Nome del prodotto prezzo (float): Prezzo unitario quantita (int): Quantità in magazzino """ self.nome = nome self._prezzo = 0 # Inizializziamo a 0 self.prezzo = prezzo # Usiamo il setter per validazione self.quantita = quantita self._sconto_percentuale = 0 # Property per il prezzo @property def prezzo(self): """Restituisce il prezzo del prodotto.""" return self._prezzo @prezzo.setter def prezzo(self, valore): """ Imposta il prezzo con validazione. Args: valore (float): Il nuovo prezzo Raises: ValueError: Se il prezzo non è positivo """ if valore <= 0: raise ValueError(f"Il prezzo deve essere positivo, ricevuto: €{valore:.2f}") self._prezzo = valore # Property per lo sconto @property def sconto_percentuale(self): """Restituisce lo sconto percentuale.""" return self._sconto_percentuale @sconto_percentuale.setter def sconto_percentuale(self, valore): """ Imposta lo sconto con validazione. Args: valore (float): Lo sconto in percentuale (0-100) Raises: ValueError: Se lo sconto non è tra 0 e 100 """ if not 0 <= valore <= 100: raise ValueError(f"Lo sconto deve essere tra 0 e 100, ricevuto: {valore}%") self._sconto_percentuale = valore # Property di sola lettura per il prezzo scontato @property def prezzo_scontato(self): """ Calcola il prezzo con lo sconto applicato. Returns: float: Il prezzo finale dopo lo sconto """ return self.prezzo * (1 - self.sconto_percentuale / 100) # Property di sola lettura per il valore inventario @property def valore_inventario(self): """ Calcola il valore totale dell'inventario. Returns: float: prezzo_scontato * quantita """ return self.prezzo_scontato * self.quantita def __str__(self): """Rappresentazione testuale del prodotto.""" info = f"{self.nome} - €{self.prezzo:.2f}" if self.sconto_percentuale > 0: info += f" (sconto {self.sconto_percentuale:.0f}% → €{self.prezzo_scontato:.2f})" info += f" x {self.quantita} = €{self.valore_inventario:.2f}" return info # Test della classe if __name__ == "__main__": prodotto = Prodotto("Laptop", 1000.0, 5) print(prodotto) print() print("Applicando sconto del 20%...") prodotto.sconto_percentuale = 20 print(f"Prezzo scontato: €{prodotto.prezzo_scontato:.2f}") print(f"Valore inventario: €{prodotto.valore_inventario:.2f}") print(prodotto) print() print("Modificando il prezzo...") prodotto.prezzo = 1200 print(prodotto) print() # Test validazione try: print("Tentativo di impostare prezzo negativo...") prodotto.prezzo = -50 except ValueError as e: print(f"❌ Errore: {e}") try: print("Tentativo di impostare sconto invalido...") prodotto.sconto_percentuale = 150 except ValueError as e: print(f"❌ Errore: {e}")
Gerarchia di Veicoli
⭐⭐⭐⭐ Medio-Difficile📝 Traccia
Crea una gerarchia di classi per rappresentare diversi tipi di veicoli. Questo esercizio mette in pratica ereditarietà, polimorfismo e metodi speciali.
✅ Requisiti
-
Classe base Veicolo:
- Attributi:
marca,modello,anno - Metodo
descrizione()che restituisce "Marca Modello (Anno)" - Metodo
emissioni_co2()che sollevaNotImplementedError(deve essere implementato dalle sottoclassi) - Metodo
__str__che usadescrizione()
- Attributi:
-
Classe Auto (eredita da Veicolo):
- Attributo aggiuntivo:
cilindrata(in litri) - Override
emissioni_co2(): restituiscecilindrata * 120g/km - Metodo
__repr__per rappresentazione tecnica
- Attributo aggiuntivo:
-
Classe Moto (eredita da Veicolo):
- Attributo aggiuntivo:
cilindrata(in cc) - Override
emissioni_co2(): restituiscecilindrata * 0.08g/km
- Attributo aggiuntivo:
-
Classe VeicoloElettrico (eredita da Veicolo):
- Attributo aggiuntivo:
capacita_batteria(in kWh) - Override
emissioni_co2(): restituisce 0 - Property
autonomia(sola lettura):capacita_batteria * 5km
- Attributo aggiuntivo:
-
Crea una funzione
confronta_emissioni(veicolo1, veicolo2)che:- Accetta due veicoli qualsiasi (polimorfismo!)
- Stampa quale veicolo inquina di meno
auto = Auto("Fiat", "Panda", 2020, 1.2)
moto = Moto("Ducati", "Monster", 2021, 821)
elettrica = VeicoloElettrico("Tesla", "Model 3", 2023, 75)
print(auto) # Fiat Panda (2020)
print(f"Emissioni: {auto.emissioni_co2():.1f} g/km") # 144.0 g/km
print(elettrica)
print(f"Emissioni: {elettrica.emissioni_co2():.1f} g/km") # 0.0 g/km
print(f"Autonomia: {elettrica.autonomia} km") # 375 km
confronta_emissioni(auto, elettrica)
# La Tesla Model 3 inquina meno della Fiat Panda
💡 Suggerimenti
- Chiama sempre
super().__init__(...)nelle sottoclassi - Per
NotImplementedError:raise NotImplementedError("messaggio") - Il polimorfismo permette di chiamare
emissioni_co2()su qualsiasi veicolo - Usa
type(obj).__name__per ottenere il nome della classe
✅ Soluzione Proposta
class Veicolo: """Classe base per tutti i veicoli.""" def __init__(self, marca, modello, anno): """ Inizializza un veicolo generico. Args: marca (str): Marca del veicolo modello (str): Modello del veicolo anno (int): Anno di produzione """ self.marca = marca self.modello = modello self.anno = anno def descrizione(self): """Restituisce una descrizione del veicolo.""" return f"{self.marca} {self.modello} ({self.anno})" def emissioni_co2(self): """ Calcola le emissioni di CO2 in g/km. Deve essere implementato dalle sottoclassi. """ raise NotImplementedError("Le sottoclassi devono implementare emissioni_co2()") def __str__(self): return self.descrizione() class Auto(Veicolo): """Rappresenta un'automobile.""" def __init__(self, marca, modello, anno, cilindrata): """ Inizializza un'auto. Args: marca (str): Marca dell'auto modello (str): Modello dell'auto anno (int): Anno di produzione cilindrata (float): Cilindrata del motore in litri """ super().__init__(marca, modello, anno) self.cilindrata = cilindrata def emissioni_co2(self): """ Calcola le emissioni di CO2. Returns: float: Emissioni in g/km (cilindrata * 120) """ return self.cilindrata * 120 def __repr__(self): return f"Auto(marca='{self.marca}', modello='{self.modello}', anno={self.anno}, cilindrata={self.cilindrata})" class Moto(Veicolo): """Rappresenta una motocicletta.""" def __init__(self, marca, modello, anno, cilindrata): """ Inizializza una moto. Args: marca (str): Marca della moto modello (str): Modello della moto anno (int): Anno di produzione cilindrata (int): Cilindrata del motore in cc """ super().__init__(marca, modello, anno) self.cilindrata = cilindrata def emissioni_co2(self): """ Calcola le emissioni di CO2. Returns: float: Emissioni in g/km (cilindrata * 0.08) """ return self.cilindrata * 0.08 class VeicoloElettrico(Veicolo): """Rappresenta un veicolo elettrico.""" def __init__(self, marca, modello, anno, capacita_batteria): """ Inizializza un veicolo elettrico. Args: marca (str): Marca del veicolo modello (str): Modello del veicolo anno (int): Anno di produzione capacita_batteria (float): Capacità della batteria in kWh """ super().__init__(marca, modello, anno) self.capacita_batteria = capacita_batteria def emissioni_co2(self): """ I veicoli elettrici non hanno emissioni dirette. Returns: float: 0 g/km """ return 0 @property def autonomia(self): """ Calcola l'autonomia stimata. Returns: float: Autonomia in km (capacita_batteria * 5) """ return self.capacita_batteria * 5 def confronta_emissioni(veicolo1, veicolo2): """ Confronta le emissioni di CO2 di due veicoli. Dimostra il polimorfismo! Args: veicolo1 (Veicolo): Primo veicolo veicolo2 (Veicolo): Secondo veicolo """ em1 = veicolo1.emissioni_co2() em2 = veicolo2.emissioni_co2() print(f" 🔍 Confronto Emissioni:") print(f" {veicolo1.descrizione()}: {em1:.1f} g/km CO2") print(f" {veicolo2.descrizione()}: {em2:.1f} g/km CO2") if em1 < em2: print(f"✅ {veicolo1.descrizione()} inquina meno!") elif em2 < em1: print(f"✅ {veicolo2.descrizione()} inquina meno!") else: print("⚖️ Entrambi hanno le stesse emissioni!") # Test delle classi if __name__ == "__main__": auto = Auto("Fiat", "Panda", 2020, 1.2) moto = Moto("Ducati", "Monster", 2021, 821) elettrica = VeicoloElettrico("Tesla", "Model 3", 2023, 75) print("🚗 === AUTO ===") print(auto) print(f"Emissioni: {auto.emissioni_co2():.1f} g/km") print(f"Repr: {repr(auto)}") print(" 🏍️ === MOTO ===") print(moto) print(f"Emissioni: {moto.emissioni_co2():.1f} g/km") print(" ⚡ === VEICOLO ELETTRICO ===") print(elettrica) print(f"Emissioni: {elettrica.emissioni_co2():.1f} g/km") print(f"Autonomia: {elettrica.autonomia:.0f} km") # Polimorfismo in azione! confronta_emissioni(auto, elettrica) confronta_emissioni(moto, auto) # Lista polimorfica print(" 📊 === TUTTI I VEICOLI ===") veicoli = [auto, moto, elettrica] for v in veicoli: print(f" {v} → {v.emissioni_co2():.1f} g/km")
Sistema Gestione Playlist Musicale
⭐⭐⭐⭐⭐ Difficile📝 Traccia
Crea un sistema completo per gestire playlist musicali. Questo esercizio integra tutti i concetti visti nella lezione: classi, ereditarietà, polimorfismo, property, metodi speciali, composizione e incapsulamento.
✅ Requisiti
-
Classe Brano:
- Attributi:
titolo,artista,durata_secondi - Property
durata_formattata(sola lettura): restituisce "MM:SS" - Metodo
__str__: "Titolo - Artista (MM:SS)" - Metodo
__repr__per debugging - Metodi
__eq__e__lt__per confronti (confronta per durata)
- Attributi:
-
Classe Playlist:
- Attributi:
nome, lista di brani (inizialmente vuota) - Metodo
aggiungi_brano(brano): aggiunge un brano - Metodo
rimuovi_brano(brano): rimuove un brano - Property
durata_totale(sola lettura): somma durate di tutti i brani - Property
durata_totale_formattata(sola lettura): formato "HH:MM:SS" - Metodo
__len__: restituisce il numero di brani - Metodo
__getitem__: permette accesso con indiceplaylist[i] - Metodo
__contains__: permettebrano in playlist - Metodo
ordina_per_durata(): ordina i brani per durata - Metodo
stampa_playlist(): mostra tutti i brani numerati
- Attributi:
-
Classe PlaylistSmart (eredita da Playlist):
- Override
aggiungi_brano: non permette duplicati - Metodo
shuffle(): mescola l'ordine dei brani - Property
brano_piu_lungo(sola lettura): restituisce il brano più lungo
- Override
# Creiamo alcuni brani
brano1 = Brano("Bohemian Rhapsody", "Queen", 354)
brano2 = Brano("Stairway to Heaven", "Led Zeppelin", 482)
brano3 = Brano("Hotel California", "Eagles", 391)
# Creiamo una playlist
playlist = PlaylistSmart("Rock Classics")
playlist.aggiungi_brano(brano1)
playlist.aggiungi_brano(brano2)
playlist.aggiungi_brano(brano3)
print(f"Brani nella playlist: {len(playlist)}") # 3
print(f"Durata totale: {playlist.durata_totale_formattata}") # 00:20:27
playlist.stampa_playlist()
print(f"Il brano più lungo è: {playlist.brano_piu_lungo}")
💡 Suggerimenti
- Per formattare MM:SS:
f"{minuti:02d}:{secondi:02d}" - Per HH:MM:SS calcola ore, poi minuti, poi secondi rimanenti
__eq__confronta titolo E artista per uguaglianza__lt__confronta la durata:self.durata_secondi < other.durata_secondi- Per shuffle usa
import randomerandom.shuffle(lista) - Per trovare il brano più lungo usa
max(self.brani, key=lambda b: b.durata_secondi) - In PlaylistSmart controlla duplicati con
if brano not in self.brani:
✅ Soluzione Proposta
import random class Brano: """Rappresenta un brano musicale.""" def __init__(self, titolo, artista, durata_secondi): """ Inizializza un brano. Args: titolo (str): Titolo del brano artista (str): Nome dell'artista durata_secondi (int): Durata in secondi """ self.titolo = titolo self.artista = artista self.durata_secondi = durata_secondi @property def durata_formattata(self): """ Restituisce la durata in formato MM:SS. Returns: str: Durata formattata """ minuti = self.durata_secondi // 60 secondi = self.durata_secondi % 60 return f"{minuti:02d}:{secondi:02d}" def __str__(self): """Rappresentazione user-friendly.""" return f"{self.titolo} - {self.artista} ({self.durata_formattata})" def __repr__(self): """Rappresentazione tecnica.""" return f"Brano(titolo='{self.titolo}', artista='{self.artista}', durata={self.durata_secondi}s)" def __eq__(self, other): """Due brani sono uguali se hanno stesso titolo e artista.""" if not isinstance(other, Brano): return False return self.titolo == other.titolo and self.artista == other.artista def __lt__(self, other): """Confronta brani per durata (per ordinamento).""" return self.durata_secondi < other.durata_secondi class Playlist: """Gestisce una playlist di brani musicali.""" def __init__(self, nome): """ Inizializza una playlist. Args: nome (str): Nome della playlist """ self.nome = nome self.brani = [] # Lista di oggetti Brano def aggiungi_brano(self, brano): """ Aggiunge un brano alla playlist. Args: brano (Brano): Il brano da aggiungere """ self.brani.append(brano) print(f"✅ Aggiunto: {brano.titolo}") def rimuovi_brano(self, brano): """ Rimuove un brano dalla playlist. Args: brano (Brano): Il brano da rimuovere Returns: bool: True se rimosso, False se non trovato """ if brano in self.brani: self.brani.remove(brano) print(f"❌ Rimosso: {brano.titolo}") return True print(f"⚠️ Brano non trovato: {brano.titolo}") return False @property def durata_totale(self): """ Calcola la durata totale della playlist in secondi. Returns: int: Durata totale in secondi """ return sum(brano.durata_secondi for brano in self.brani) @property def durata_totale_formattata(self): """ Restituisce la durata totale in formato HH:MM:SS. Returns: str: Durata formattata """ totale = self.durata_totale ore = totale // 3600 minuti = (totale % 3600) // 60 secondi = totale % 60 return f"{ore:02d}:{minuti:02d}:{secondi:02d}" def __len__(self): """Restituisce il numero di brani nella playlist.""" return len(self.brani) def __getitem__(self, index): """Permette l'accesso ai brani tramite indice.""" return self.brani[index] def __contains__(self, brano): """Permette di usare 'in' per controllare se un brano è nella playlist.""" return brano in self.brani def ordina_per_durata(self, reverse=False): """ Ordina i brani per durata. Args: reverse (bool): Se True ordina dal più lungo al più corto """ self.brani.sort(reverse=reverse) print(f"🔄 Playlist ordinata per durata ({'decrescente' if reverse else 'crescente'})") def stampa_playlist(self): """Stampa tutti i brani della playlist.""" print(f" 🎵 === {self.nome.upper()} ===") print(f"Brani: {len(self)} | Durata totale: {self.durata_totale_formattata}") print("-" * 50) for i, brano in enumerate(self.brani, 1): print(f"{i:2d}. {brano}") class PlaylistSmart(Playlist): """Playlist con funzionalità avanzate.""" def aggiungi_brano(self, brano): """ Override: non permette duplicati. Args: brano (Brano): Il brano da aggiungere Returns: bool: True se aggiunto, False se duplicato """ if brano in self.brani: print(f"⚠️ Brano già presente: {brano.titolo}") return False super().aggiungi_brano(brano) # Chiama il metodo della classe base return True def shuffle(self): """Mescola casualmente l'ordine dei brani.""" random.shuffle(self.brani) print("🔀 Playlist mescolata casualmente!") @property def brano_piu_lungo(self): """ Restituisce il brano più lungo della playlist. Returns: Brano: Il brano con durata maggiore, None se playlist vuota """ if not self.brani: return None return max(self.brani, key=lambda b: b.durata_secondi) # ======================================== # TEST COMPLETO DEL SISTEMA # ======================================== if __name__ == "__main__": # Creiamo alcuni brani print("🎸 Creazione brani...") brano1 = Brano("Bohemian Rhapsody", "Queen", 354) brano2 = Brano("Stairway to Heaven", "Led Zeppelin", 482) brano3 = Brano("Hotel California", "Eagles", 391) brano4 = Brano("Comfortably Numb", "Pink Floyd", 382) brano5 = Brano("Sweet Child O' Mine", "Guns N' Roses", 356) # Creiamo una playlist smart print(" 📝 Creazione playlist...") playlist = PlaylistSmart("Rock Classics") # Aggiungiamo brani print(" ➕ Aggiunta brani...") playlist.aggiungi_brano(brano1) playlist.aggiungi_brano(brano2) playlist.aggiungi_brano(brano3) playlist.aggiungi_brano(brano4) playlist.aggiungi_brano(brano5) # Test duplicati print(" 🔁 Test duplicati...") playlist.aggiungi_brano(brano1) # Non verrà aggiunto # Mostriamo la playlist playlist.stampa_playlist() # Test operatori speciali print(" 🔍 Test operatori speciali...") print(f"Numero di brani: {len(playlist)}") print(f"Primo brano: {playlist[0]}") print(f"Bohemian Rhapsody è nella playlist? {brano1 in playlist}") # Brano più lungo print(" ⏱️ Brano più lungo...") print(f"Il brano più lungo è: {playlist.brano_piu_lungo}") # Ordiniamo print(" 📊 Ordinamento...") playlist.ordina_per_durata(reverse=True) playlist.stampa_playlist() # Shuffle print(" 🎲 Mescolamento casuale...") playlist.shuffle() playlist.stampa_playlist() # Rimozione print(" ➖ Rimozione brano...") playlist.rimuovi_brano(brano3) playlist.stampa_playlist() print(" ✅ Test completato!")
🎓 Conclusione
Complimenti per aver completato l'esercitazione! Se sei riuscito a risolvere tutti e 5 gli esercizi, hai dimostrato di aver compreso i concetti fondamentali della Programmazione Orientata agli Oggetti in Python.
✅ Cosa Hai Imparato
- Esercizio 1: Classi base, __init__, metodi semplici
- Esercizio 2: Incapsulamento, validazione input, getter
- Esercizio 3: Property avanzate, validazione con setter, property calcolate
- Esercizio 4: Ereditarietà, polimorfismo, metodi astratti, super()
- Esercizio 5: Integrazione completa di tutti i concetti, metodi speciali avanzati
🚀 Prossimi Passi
Ora che hai padroneggiato le basi, prova a:
- Estendere questi esercizi con funzionalità aggiuntive
- Creare progetti personali usando la OOP
- Studiare design patterns come Singleton, Factory, Observer
- Esplorare concetti avanzati come decoratori e metaclassi